package org.road0329.dlt698.asn1.axdr;

import org.road0329.dlt698.ReverseByteArrayInputStream;
import org.road0329.dlt698.ReverseByteArrayOutputStream;
import org.road0329.dlt698.axdr.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

public abstract class AxdrSequenceOf<E extends AxdrType> extends AxdrType {

    private static Logger logger = LoggerFactory.getLogger(AxdrSequenceOf.class);
    private byte[] dataCode = null;
    private int length = -1;
    private List<E> seqOf = null;

    public AxdrSequenceOf() {
        this.seqOf = new ArrayList<E>();
    }

    public void setDataCode(byte[] dataCode) {
        this.dataCode = dataCode;
        this.seqOf = new ArrayList<E>();
    }

    public void setSeq(List<E> seqOf) {
        this.seqOf = seqOf;
    }

    public void setLength(int length) {
        this.length = length;
        this.seqOf = new ArrayList<E>(length);
    }

    public byte[] getDataCode() {
        return this.dataCode;
    }

    public int decode(ReverseByteArrayInputStream input) throws Exception {
        int codeLength = 0;
        int numElements = this.length == -1 ? 0 : this.length;
        if (numElements == 0) {
            AxdrLength l = new AxdrLength(0);
            codeLength += l.decode(input);
            numElements = l.getValue();
        }
        this.seqOf = new ArrayList<E>();
        for (int i = 0; i < numElements; i++) {
            E subElem = this.createListElement();
            codeLength += subElem.decode(input);
            this.seqOf.add(subElem);
        }
        return codeLength;
    }

    public int encode(ReverseByteArrayOutputStream output) throws Exception {
        int codeLength;
        if (this.dataCode != null) {
            codeLength = this.dataCode.length;
            for (int i = this.dataCode.length - 1; i >= 0; i--) {
                output.write(this.dataCode[i]);
            }
        } else {
            if (this.length != -1 && this.length != this.seqOf.size()) {
                throw new RuntimeException("Error decoding AxdrSequenceOf: Size of elements does not match.");
            }
            codeLength = 0;
            for (int i = this.seqOf.size() - 1; i >= 0; i--) {
                codeLength += this.seqOf.get(i).encode(output);
            }
            if (this.length == -1) {
                codeLength += AxdrLength.encodeLength(output, this.seqOf.size());
            }
        }
        return codeLength;
    }

    public void encodeAndSave(int encodingSizeGuess) throws Exception {
        ReverseByteArrayOutputStream revOStream = new ReverseByteArrayOutputStream();
        revOStream.setBufSize(encodingSizeGuess);
        this.encode(revOStream);
        this.dataCode = revOStream.getArray();
    }

    public void add(E element) {
        if (this.length != 0 && this.seqOf.size() == this.length) {
            logger.debug("AxdrSequenceOf length:{} - size:{}", this.length, this.seqOf.size());
            throw new RuntimeException("array index out of bound");
        }
        this.seqOf.add(element);
    }

    public E get(int index) {
        return this.seqOf.get(index);
    }

    public int size() {
        return this.seqOf.size();
    }

    public List<E> list() {
        return this.seqOf;
    }

    public abstract E createListElement();
}



